查看原文
其他

Python VS R:双语数据分析——上海租房数据全解析

2017-10-30 董天一 Python爱好者社区

作者:董天一

个人公众号:生物与化学数据分析


房价看多了一把泪,年轻人还是多看看租房信息好了,毕竟日子还是要过滴。

由于周围的朋友很多都选择在上海发展,所以这次我把目标锁定在了上海,看看上海的租房行情怎么样。(温馨提示:看之前要调整好心态。。)


本文使用pythonR两种语言进行数据分析,尽量做到代码完整,易于理解。关注的重点在于数据清洗与分析的流程,每一部分的代码不一定是实现完全一样的效果,在对比之中实现取长补短、各显其能才是最佳的做法。感兴趣的同学们赶紧来一起练手吧!

一、数据获取和读入

使用python编写爬虫,爬取了链家网上海3W+的租房信息,包括价格、面积、朝向、户型等数据。

python中使用pandas读入数据;在R中使用readxl包读入数据。

python

import seaborn as sns
import pandas as pd import numpy as np
import matplotlib.pyplot as plt data = pd.read_excel("链家网上海租房信息汇总.xlsx") data.head()

R

library(readxl) data <- read_xlsx("链家网上海租房信息汇总.xlsx") data <- data[, -1] head(data)



二、数据概况

1、数据类型

python


R



可以看到,python和R读数据的时候还是有些差别的,python将纯数字的kanguoprice两列读成了整型,而R则将所有变量都读成了字符串,所以之后在做预处理的时候同样也要区别处理。

2、缺失值查看

python

data.apply(lambda x : sum(x.isnull()))

R

library(VIM) table(is.na(data)) aggr(data, prop = FALSE, numbers = TRUE)




python中使用apply结合lambda表达式进行缺失值查看,是常用的做法;而R中则有比较多的方法,这里采用了is.na()函数进行缺失值的全局查看,并使用VIM包aggr函数进行缺失值的可视化。无论用哪一种方法,我们所有的数据都没有观测到有缺失值,这首先要归功于链家网数据的完整性,当然更重要的,要归功于我们的爬虫写的太好了。

3、赶紧看看价格呀!

对滴对滴,拿到数据赶紧看看最重要的——价格都是多钱呀!

python

from pylab import *

mpl.rcParams['font.sans-serif'] = ['SimHei'] data["price"].plot(kind = "box", fontsize = 15) plt.ylim(0, 50000) plt.title("上海租房价格分布箱线图") plt.show()


R

library(psych) data$price <- as.numeric(data$price) describe(data$price)



这里我们利用python的pandas快捷作图作出价格分布的箱线图,用R的psych包对租房价格做一下基本的描述性统计。

从箱线图中看出一半的出租房价格集中在4000-10000的范围内,中位数为5500。

4、哎哟,一个月几十万的租金?


是呀兄弟们,我也看见了,上面统计的时候,最高的租金是每个月58W?

此时按捺不住好奇心的我取出了每月租金高于10W的子集。

python

data[data["price"] > 100000]

R

data[data$price > 100000, ]



居然有25套房子每个月要10W以上的租金!我们随便找一个房子的信息百度一下看看到底是啥样的房子。。对!就找那个最贵的58W的!凤阳路338号!9室9厅,1338平,我滴妈???


打开网页的我是崩溃的。







古董洋房我去!一个月才58W呢,真是白菜价哟!

三、数据清洗和单一变量分析

1、供水区和楼层

ceng的一列中包含了两部分信息,供水区和楼层,我们它拆成两个变量。(低区、中区、高区指的原来是不同供水方式的区域,我也是百度到的,真是涨姿势呀。)

python

import re a = r"(.*?)区"
b = r"\d+"
def get_water_supply_region(x):    if len(re.findall(a, x)) == 0:        
       return "无"    else:        
       return re.findall(a,x)[0] data["ceng"] = data["ceng"].apply(lambda x : x.strip()) data["water_supply_region"] = data["ceng"].apply(get_water_supply_region) data["floor"] = data["ceng"].apply(lambda x : re.findall(b, x)[0]) data["floor"] = data["floor"].astype(np.int64) data.drop("ceng", axis = 1, inplace = True)
data["water_supply_region"].value_counts().plot(kind = "bar") plt.xticks(rotation = 0) plt.xlabel("供水区") plt.ylabel("count") plt.title("上海租房供水区分布柱状图") plt.show()


R

library(stringr) data$water_supply_region <- str_extract(data$ceng, "(.*?)区") data$water_supply_region[is.na(data$water_supply_region)] <- "无"
data$floor <- str_extract(data$ceng, "[:digit:]+层") data$floor <- str_extract(data$floor, "[:digit:]+") data$floor <- as.numeric(data$floor) data <- data[,-1] table(data$water_supply_region)


使用正则表达式提取相应数据,python中使用re库,R中使用stringr包,都很方便,不过两种语言的正则表达式语法稍有不同,需要分别学习。

从柱状图中可以看出,高区和中区的房屋要多于地区的房屋,不愧是大魔都呀。

由于楼层信息较为零散,我们将其切段,0代表1-5层,1代表6-10层,2代表11-20层,3代表21-30层,4代表30层以上。

python

data["floor"] = data["floor"].apply(lambda x : 0 if (1 <= x <= 5) else x) data["floor"] = data["floor"].apply(lambda x : 1 if (6 <= x <= 10) else x) data["floor"] = data["floor"].apply(lambda x : 2 if (11 <= x <= 20) else x) data["floor"] = data["floor"].apply(lambda x : 3 if (21 <= x <= 30) else x) data["floor"] = data["floor"].apply(lambda x : 4 if (x > 30) else x) aa = data["floor"].value_counts() cc = pd.DataFrame(aa) cc.columns = ["count"] cc["floor"] = cc.index cc = cc.sort_values(by = "floor") plt.bar(cc["floor"], cc["count"]) plt.xticks(cc["floor"], ["1-5层", "6-10层", "11-20层", "21-30层", "30层以上"], rotation = 0) plt.xlabel("楼层") plt.ylabel("count") plt.title("上海租房楼层分布柱状图") plt.show()


R

data$floor2 <- data$floor data$floor2[data$floor >= 1 & data$floor <= 5] <- "1-5层"
data$floor2[data$floor >= 6 & data$floor <= 10] <- "6-10层"
data$floor2[data$floor >= 11 & data$floor <= 20] <- "11-20层"
data$floor2[data$floor >= 21 & data$floor <= 30] <- "21-30层"
data$floor2[data$floor > 30] <- "30层以上"

library(sqldf)
library(ggplot2) plot_set <- sqldf("select floor2, count(region) as counts from data group by floor2") plot_set$floor2 <- factor(plot_set$floor2, levels = c("1-5层","6-10层", "11-20层", "21-30层", "30层以上")) ggplot(plot_set, aes(x = floor2, y = counts)) + geom_bar(stat = "identity", width = .5) + theme(axis.title = element_text(size = 18), axis.text = element_text(size = 16))



从图中可以看出,上海出租房的楼层分布主要集中在6-10层,以及20层以内。

2、朝向

我们先看看朝向的取值都有哪些:

python


R



有将近一半的房屋没有朝向的数据(在爬取数据时就用“无朝向数据”填充了),其他的各种朝向都有,我们来可视化一下。

python

data["chaoxiang"].value_counts().plot(kind = "bar") plt.xlabel("朝向") plt.ylabel("count") plt.title("上海租房朝向分布柱状图") plt.show()


R

plot_set2 <- sqldf("select chaoxiang, count(region) as counts from data group by chaoxiang order by counts DESC") ggplot(plot_set2, aes(x = chaoxiang, y = counts)) + geom_bar(stat = "identity", width = .5) + theme(axis.title = element_text(size = 18), axis.text = element_text(size = 16), axis.text.x = element_text(angle = 30))



在拥有朝向数据的房屋中,朝南和朝南北的占绝大多数。“朝南北”的房屋对大多数人更具有吸引力,中介也往往用“南北通透”来做宣传。

既然不朝南或者不是南北通透的房子都是“奇葩”,那我们干脆将其他的朝向都纳入“其他”了事。

python

qita = ["朝北", "朝东", "朝东北", "朝东南", "朝东西", "朝西", "朝西北", "朝西南"]
for each in qita:    data["chaoxiang"] = data["chaoxiang"].apply(lambda x : "其他" if x == each else x)

R

qita <- c("朝北", "朝东", "朝东北", "朝东南", "朝东西", "朝西", "朝西北", "朝西南")
for(i in qita){data$chaoxiang[data$chaoxiang == i] <- "其他"}

3、发布日期

发布日期不是我们重点关注的点,这里我们把发布日期的字符串转为时间序列,做一下可视化,然后丢掉之。

python

data["date"] = data["date"].apply(lambda x : x[: 10]) data["date"] = pd.to_datetime(data["date"]) data["date"].value_counts().plot() plt.xlabel("日期") plt.ylabel("count") plt.title("上海租房发布数量走势") plt.show() data.drop("date", axis = 1, inplace = True)


R

data$date <- substr(data$date, 1, 10) data$date <- as.Date(data$date, "%Y.%m.%d") plot_set3 <- sqldf("select date, count(region) as counts from data group by date order by date") plot(plot_set3$date, plot_set3$counts, "l") data <- data[, -2]



ok,看到从今年三月份开始,链家网上海的租房信息才开始逐渐增多的。

4、带看次数

带看次数的取值也比较多,我们也采取切段的方式处理:0代表0次,1代表1次,2代表2-5次,3代表6-10次,4代表10次以上。

python

data["kanguo"] = data["kanguo"].apply(lambda x : 2 if (2 <= x <= 5) else x) data["kanguo"] = data["kanguo"].apply(lambda x : 3 if (6 <= x <= 10) else x) data["kanguo"] = data["kanguo"].apply(lambda x : 4 if (x > 10) else x) data["kanguo"].value_counts().plot(kind = "bar") plt.xlabel("带看次数") plt.ylabel("count") plt.title("上海租房带看次数分布柱状图") plt.xticks([0, 1, 2, 3, 4], ["0", "2-5", "1", "6-10", "> 10"], rotation = 0) plt.show()

R

data$kanguo <- as.numeric(data$kanguo) data$kanguo2 <- data$kanguo data$kanguo2[data$kanguo == 0] <- "0次"
data$kanguo2[data$kanguo == 1] <- "1次"
data$kanguo >= 2 & data$kanguo <= 5] <- "2-5次"
data$kanguo2[data$kanguo >= 6 & data$kanguo <= 10] <- "6-10次"
data$kanguo2[data$kanguo > 10] <- "多于10次"

plot_set4 <- sqldf("select kanguo2, count(region) as counts from data group by kanguo2") ggplot(plot_set4, aes(x = kanguo2, y = counts)) + geom_bar(stat = "identity", width = .5) + theme(axis.title = element_text(size = 18), axis.text = element_text(size = 16))


有三分之一多的房屋还静静地躺着,并没有人去看过;看过6次以上的抢手出租房有约5000套,不过看了这么多次也有可能是大坑,一定要小心选择啊。

5、面积

面积数据在爬取的时候周围有换行符等等干扰,所以要清理一下,再转成整型。这里依然使用正则表达式提取关键数据。

python

a = r"(.*?)平"
data["mianji"] = data["mianji"].apply(lambda x : re.findall(a, x)[0]) data["mianji"] = data["mianji"].astype(np.int64)

R

data$mianji <- str_extract(data$mianji, "(.*?)平") data$mianji <- str_extract(data$mianji, "[:digit:]+")

6、区

各区的数据比较完整,取值也不多,来看看各区房屋数量多少吧。

python

data["region"].value_counts().plot(kind = "barh") plt.xlabel("count") plt.title("上海各区租房数量分布条形图") plt.show()


R

plot_set5 <- sqldf("select region, count(chaoxiang) as counts from data group by region order by counts DESC") ggplot(plot_set5, aes(x = region, y = counts)) + geom_bar(stat = "identity", width = .5) + theme(axis.title = element_text(size = 18), axis.text = element_text(size = 16)) + coord_flip()



浦东出租房数量遥遥领先于其他区,闵行、徐汇和宝山位于2-4位,最少的崇明在链家网上只有7套房屋在出租。

7、户型

户型的数据格式为X室X厅,我们先对各种户型出租房的数量进行可视化分析,然后将其拆分为roomsliving_rooms两列。

python

data["shiting"] = data["shiting"].apply(lambda x : x.strip()) aa = data["shiting"].value_counts()
#将计数为500以下的户型都纳入“其他”
qita = aa[: 10].sum() aa = aa[: 10] aa["其他"] = qita cc = pd.DataFrame(aa) cc["huxing"] = cc.index fig, (ax, ax2) = plt.subplots(2, 1, sharex=True) ax.bar(range(1, 12), cc["shiting"]) ax2.bar(range(1, 12), cc["shiting"]) ax.set_ylim(28000, 30000) ax2.set_ylim(0, 10000) ax.spines["bottom"].set_visible(False) ax2.spines["top"].set_visible(False) ax.xaxis.tick_top() ax.tick_params(labeltop = "off") ax2.xaxis.tick_bottom() plt.xticks(range(1, 12), list(cc["huxing"]), rotation = 60)
#绘制坐标轴截断线
d = .015
kwargs = dict(transform=ax.transAxes, color='k', clip_on=False) ax.plot((-d, +d), (-d, +d), **kwargs) ax.plot((1 - d, 1 + d), (-d, +d), **kwargs) kwargs.update(transform=ax2.transAxes) ax2.plot((-d, +d), (1 - d, 1 + d), **kwargs) ax2.plot((1 - d, 1 + d), (1 - d, 1 + d), **kwargs) plt.xlabel("户型") ax.set_title("上海租房户型分布柱状图") ax.set_ylabel("count", position = (0, 0)) plt.show()

#拆分
data["rooms"] = data["shiting"].apply(lambda x : re.findall(r"(.*?)室", x)[0]) data["living_rooms"] = data["shiting"].apply(lambda x : re.findall(r"室(.*?)厅", x)[0]) data["rooms"] = data["rooms"].astype(np.int64) data["living_rooms"] = data["living_rooms"].astype(np.int64) data.drop("shiting", axis = 1, inplace = True)



这里将数量较少的户型并入“其他”,然后采用截断式柱状图进行可视化。

户型种类繁多,尤其还包括上面“超级洋房”9室9厅那种。不过最多的还是2室2厅,3室2厅,2室1厅这样的家庭、合租式的住房以及1室1厅这样的单身出租房。

使用R作截断式柱状图比较繁琐,下面只给出拆分列的代码,可视化代码略去

R

data$rooms <- str_extract(data$shiting, "[:digit:]+室") data$living_rooms <- str_extract(data$shiting, "室[:digit:]+厅") data$rooms <- str_extract(data$rooms, "[:digit:]+") data$living_rooms <- str_extract(data$living_rooms, "[:digit:]+") data <- data[, -6]

8、街道、小区、标题

街道、小区和标题信息太过杂乱,而数据量又并不大,所以选择去掉。

python

data.drop(["street", "xiaoqu", "title"], axis = 1, inplace = True)

R

data <- data[, c(-6, -7, -8)]

9、变量类型和其他处理

对于python中的数据框,带看数和层数在切段后要转为字符串型;对于R中的数据框,切段前的带看数和层数需要去掉,几列数值型变量需要转换,直接看代码。

python

data["kanguo"] = data["kanguo"].astype(str) data["floor"] = data["floor"].astype(str) data.head()



R

data <- data[, c(-2, -7)] data$mianji <- as.numeric(data$mianji) data$rooms <- as.numeric(data$rooms) data$living_rooms <- as.numeric(data$living_rooms) head(data)



四、多变量分析

通过多变量的分析,可以实现一些直观的想法,比如不同区内房屋价格的分布情况是怎样的?

python

sns.boxplot(x = "price", y = "region", data = data) plt.title("上海各区租房价格分布箱线图") plt.xlim(0, 40000) plt.show()

R

ggplot(data, aes(x = region, y = price, fill = region)) + geom_boxplot(size = 1) + coord_flip() + ylim(0, 40000) + scale_fill_hue()



从箱线图中看出,静安的租房价格最高,百度了一下,果然,静安区是市中心呀。

接下来可以看看各区租房面积的分布是怎样的。

python

sns.boxplot(x = "mianji", y = "region", data = data) plt.title("上海各区租房面积分布箱线图") plt.xlim(0, 1000) plt.show()

R

ggplot(data, aes(x = region, y = mianji, fill = region)) + geom_boxplot(size = 1) + coord_flip() + ylim(0, 1000) + scale_fill_hue()



好吧,面积分布都差不多,青浦的出租房面积稍大一些。

和买房一样,租房也要看单价的呀,我们再来看下各区房屋单价的分布吧。

python

data["unit_price"] = data["price"] / data["mianji"] sns.boxplot(x = "unit_price", y = "region", data = data) plt.title("上海各区租房单价分布箱线图") plt.xlim(0, 600) plt.show()

R

data$unit_price <- data$price / data$mianji ggplot(data, aes(x = region, y = unit_price, fill = region)) + geom_boxplot(size = 1) + coord_flip() + ylim(0, 600) + scale_fill_hue()



看起来还是静安价格最高,不过高低排名看得不是很清晰。我们把各区单价的中位数提取出来作漏斗图,就清楚多了:

python

aa = data["unit_price"].groupby(data["region"]).median().sort_values(ascending = True) cc = pd.DataFrame(aa) cc["region"] = cc.index cc["place_holder"] = (150 - cc["unit_price"]) / 2
fig, ax = plt.subplots() ax.barh(range(len(cc)), cc["unit_price"], align = "center", left = cc["place_holder"]) plt.yticks(range(len(cc)), cc["region"], rotation = 0) labels = ["%.2f"%each for each in cc["unit_price"]]
for i in range(len(cc.iloc[4:, :])):    ax.text(x = 69, y = [k for k in range(len(cc))][i] - .2, s = labels[i], color = "black", size = 10)
for i in range(len(cc.iloc[0:4, :])):    ax.text(x = 67.3, y = [14,15,16,17][i] - .2, s = labels[i + 14], color = "black", size = 10) plt.xlabel("unit_price") plt.title("上海各区租房单价中位数漏斗图") plt.show()

R

plot_set6 <- aggregate(data$unit_price, by = list(data$region), median) names(plot_set6) <- c("region", "price_median")
plot_set6 <- sqldf("select * from plot_set6 order by price_median") plot_set6$price_median <- round(plot_set6$price_median, 2) plot_set6$placeholder <- round((150 - plot_set6$price_median) / 2, 2) new_plot_set6 <- melt(plot_set6, id = "region") new_plot_set6$region <- factor(new_plot_set6$region, levels = plot_set6$region) new_plot_set6$variable <- factor(new_plot_set6$variable, levels = c("price_median", "placeholder"))
p1 <- ggplot(new_plot_set6) + geom_bar(aes(x = region, y = value, fill = variable), stat = "identity") p2 <- p1 + coord_flip() + guides(fill = F) + theme(panel.background = element_blank()) p3 <- p2 + scale_fill_manual(values = c("steelblue2", "white")) p4 <- p3 + annotate("text", x = 1:18, y = 75, label = plot_set6$price_median) p4



用漏斗图来看就清楚多了,租房单价最高的是静安区,平均每平米要将近150块;接着是黄浦、长宁和徐汇;房屋出租数量最多的浦东价格在中间的位置,平均每平米78块多,听起来还可以的样子。而在崇明,租一个一百平的房子一个月只要两千多(虽然数据量太少),真是适宜生存呢。

接着我们再来看一下,是不是真的“南北通透”的房子价格会高一些呢?

因为奇葩的价格还是比较多的,我们还是用带有中位数线的箱线图来看。

python

sns.boxplot(x = "chaoxiang", y = "price", data = data) plt.xlabel("朝向") plt.ylabel("价格") plt.title("上海租房各朝向价格分布箱线图") plt.ylim(0,50000) plt.show()

R

ggplot(data, aes(x = chaoxiang, y = price)) + geom_boxplot() + ylim(0, 50000)



从箱线图中可以看出,“朝南北”的房屋价格是要高一些。



五、房价预测

最后再来一波简单的房价预测吧。我们用python实现随机森林算法,用于预测房价。

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import r2_score
new_data = pd.get_dummies(data) X = new_data.drop("price", axis = 1).values y = new_data["price"].values X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = .3, random_state = 71)
rf = RandomForestRegressor(n_estimators = 1000, n_jobs = 7, max_depth = 10, random_state = 71) rf.fit(X_train, y_train) y_train_pred = rf.predict(X_train) y_test_pred = rf.predict(X_test) print("R^2 train: %.3f, test: %.3f" % (r2_score(y_train, y_train_pred), r2_score(y_test, y_test_pred)))

可以看到效果还可以,但是在测试集上的结果出现了过拟合,需要进一步的参数优化。

ok,上海租房情况就分析到这里,魔都的租房行情已经了解的差不多了。数据不多,但分析的过程比较长,耐心看完,一起操作,一定会有不小的提高!


文末扫码关注Python爱好者社区,后台回复‘上海租房’即可获得:


本文爬虫源代码.py和爬取数据汇总 + 数据分析流程Ipython notebook + 数据分析流程R Script

Python爱好者社区历史文章大合集

Python爱好者社区历史文章列表(每周append更新一次)

福利:文末扫码立刻关注公众号,“Python爱好者社区”,开始学习Python课程:

关注后在公众号内回复“课程”即可获取:

1.崔老师爬虫实战案例免费学习视频。

2.丘老师数据科学入门指导免费学习视频。

3.陈老师数据分析报告制作免费学习视频。

4.玩转大数据分析!Spark2.X+Python 精华实战课程免费学习视频。

5.丘老师Python网络爬虫实战免费学习视频。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存